package com.gzsxy.esjy.message.netty;


import com.gzsxy.esjy.message.dispatch.MessageDispatch;
import com.gzsxy.esjy.message.netty.decoder.HttpRequestDecoder;
import com.gzsxy.esjy.message.netty.encoder.WebsocketEncoder;
import com.gzsxy.esjy.message.netty.handler.InnerPortolHandler;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.timeout.IdleStateHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

import java.util.concurrent.TimeUnit;

/**
 * netty启动服务
 * CommandLineRunner接口来实现Spring容器启动完成后执行的功能   DisposableBean后者在Bean生命周期结束前调用destory()方法做一些收尾工作。
 * static代码块  -> 构造方法 -> @PostConstruct注解 -> ApplicationRunner和CommandLineRunner
 * @author zhangxiaolong
 *
 */
@Component
public class NettyServer implements CommandLineRunner,DisposableBean{

	private static final Logger log = LoggerFactory.getLogger(NettyServer.class);

	@Value("${server.port}")
	private int port ;
	
	@Value("${socket.heartbeat.interval:10}")
	private int interval ;
	
	@Autowired
	private MessageDispatch messageDispatch ;
	/**
	 * 主通道
	 */
	protected Channel channel = null ;
	
	/**
	 * bootstrap服务
	 */
	protected ServerBootstrap bootstrap ;
	
	private EventLoopGroup bossGroup;

	private EventLoopGroup workerGroup;


//	@PostConstruct
    private void start() {
    	init() ;
        ChannelFuture channelFuture = bootstrap.bind(port).syncUninterruptibly().addListener(future -> {
            String logBanner = "\n"+
            		"█▀▄▀█ ▄███▄     ▄▄▄▄▄    ▄▄▄▄▄   ██     ▄▀  ▄███▄       ▄█▄    ▄███▄      ▄     ▄▄▄▄▀ ▄███▄   █▄▄▄▄\n"+ 
            		"█ █ █ █▀   ▀   █     ▀▄ █     ▀▄ █ █  ▄▀    █▀   ▀      █▀ ▀▄  █▀   ▀      █ ▀▀▀ █    █▀   ▀  █  ▄▀\n"+ 
            		"█ ▄ █ ██▄▄   ▄  ▀▀▀▀▄ ▄  ▀▀▀▀▄   █▄▄█ █ ▀▄  ██▄▄        █   ▀  ██▄▄    ██   █    █    ██▄▄    █▀▀▌ \n"+  
            		"█   █ █▄   ▄▀ ▀▄▄▄▄▀   ▀▄▄▄▄▀    █  █ █   █ █▄   ▄▀     █▄  ▄▀ █▄   ▄▀ █ █  █   █     █▄   ▄▀ █  █ \n"+  
            		"   █  ▀███▀                         █  ███  ▀███▀       ▀███▀  ▀███▀   █  █ █  ▀      ▀███▀     █  \n"+ 
            		"  ▀                                █                                   █   ██                  ▀   \n"+ 
            	    "                                  ▀                                             Started on port %s \n" ;
            System.out.println(String.format(logBanner, port));
        });
        channel = channelFuture.channel();
        channel.closeFuture().addListener(future -> {
            log.info("message Server Start Shutdown ............");
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        });
        log.info("started in {}", port);
    }
    
    /**
     * 关闭
     */
    private void close() {
    	if(channel != null) {
    		channel.close();
    	}
    }

    /**
     * 初始化
     */
	private void init() {
		//创建服务器端的启动对象，配置参数
	    bootstrap = new ServerBootstrap();
		//创建BossGroup 和 WorkerGroup
		//说明
		//1. 创建两个线程组 bossGroup 和 workerGroup
		//2. bossGroup 只是处理连接请求 , 真正的和客户端业务处理，会交给 workerGroup完成
		//3. 两个都是无限循环
		//4. bossGroup 和 workerGroup 含有的子线程(NioEventLoop)的个数  NettyRuntime.availableProcessors()
		//   默认实际 cpu核数 * 2
	    bossGroup = new NioEventLoopGroup(1);
	    workerGroup = new NioEventLoopGroup(Runtime.getRuntime().availableProcessors());
		//设置两个线程组
	    bootstrap.group(bossGroup, workerGroup);
		//使用NioSocketChannel 作为服务器的通道实现
        bootstrap.channel(NioServerSocketChannel.class);
		//设置保持活动连接状态
        bootstrap.childOption(ChannelOption.SO_KEEPALIVE,true);
        bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
            @Override
            public void initChannel(SocketChannel ch) {
            	ChannelPipeline pipeline = ch.pipeline();
				//1. HttpServerCodec 是netty 提供的处理http的 编-解码器
            	pipeline.addLast("http-codec",new HttpServerCodec())
            	.addLast("request-codec",new HttpRequestDecoder(messageDispatch))
						/*
                          说明
                          1. IdleStateHandler 是netty 提供的处理空闲状态的处理器
                          2. long readerIdleTime : 表示多长时间没有读, 就会发送一个心跳检测包检测是否连接
                          3. long writerIdleTime : 表示多长时间没有写, 就会发送一个心跳检测包检测是否连接
                          4. long allIdleTime : 表示多长时间没有读写, 就会发送一个心跳检测包检测是否连接

                          5. 文档说明
                          triggers an {@link IdleStateEvent} when a {@link Channel} has not performed
                         read, write, or both operation for a while.
                          6. 当 IdleStateEvent 触发后 , 就会传递给管道 的下一个handler去处理，通过调用(触发)
                         下一个handler 的 userEventTiggered , 在该方法中去处理 IdleStateEvent(读空闲，写空闲，读写空闲)
                          7.handlerRemoved有时候是无法感知连接断掉，所以还是需要心跳包的检测来判断连接是否还有效
                           */
				.addLast(new IdleStateHandler(3*interval, interval, 6*interval, TimeUnit.SECONDS))
				.addLast("inner-handler",new InnerPortolHandler(messageDispatch))
            	.addLast("message-encoder",new WebsocketEncoder()) ;
            }
        });
	}

	@Override
	public void run(String... args) throws Exception {
		start();
	}
	@Override
	public void destroy() throws Exception {
		close() ;
	}
}
